#region Using Statements
using System;
using System.IO;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Xml;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
#endregion

namespace SkyBurner
{
    #region TerrainPatchBase
    public class TerrainPatchBase : BaseObject
    {
        protected Matrix m_mxWorld;
        public Matrix World
        {
            get { return m_mxWorld; }
            set { m_mxWorld = value; }
        }

        public TerrainPatchBase(BaseGame pGame)
            : base(pGame)
        {

        }

        public virtual void Draw(GameTime gameTime, GraphicsDevice pGraphicsDevice, Camera pActiveCamera)
        {

        }
    }
    #endregion

    #region InfiniteTerrainBase
    public class InfiniteTerrainBase : BaseDrawableObject
    {
        #region Protected attributes
        protected int m_nTerrainSize;
        protected int m_nTerrainPatchCount;
        protected float m_fTerrainScaleX;
        protected float m_fTerrainScaleY;
        protected float m_fTerrainScaleZ;
        protected float m_fHalfTerrainSizeX;
        protected float m_fHalfTerrainSizeZ;
        protected float m_fTerrainPatchSizeX;
        protected float m_fTerrainPatchSizeZ;
        protected float m_fTranslateY;
        protected TerrainPatchBase[,] m_terrainPatches;
        #endregion

        public InfiniteTerrainBase()
        {

        }

        public void Initialize(BaseGame game, float fTerrainScaleX, float fTerrainScaleY, float fTerrainScaleZ, int nTerrainSize, int nTerrainPatchCount, float fTranslateY)
        {
            Initialize(game);
            
            m_nTerrainSize = nTerrainSize;
            m_nTerrainPatchCount = nTerrainPatchCount;
            m_fTranslateY = fTranslateY;

            m_fTerrainScaleX = fTerrainScaleX;
            m_fTerrainScaleY = fTerrainScaleY;
            m_fTerrainScaleZ = fTerrainScaleZ;

            m_fHalfTerrainSizeX = m_nTerrainSize * m_fTerrainScaleX * 0.5f;
            m_fHalfTerrainSizeZ = m_nTerrainSize * m_fTerrainScaleZ * 0.5f;

            int nPatchSize = m_nTerrainSize / m_nTerrainPatchCount;
            m_fTerrainPatchSizeX = nPatchSize * m_fTerrainScaleX;
            m_fTerrainPatchSizeZ = nPatchSize * m_fTerrainScaleZ;

            m_terrainPatches = new TerrainPatchBase[m_nTerrainPatchCount, m_nTerrainPatchCount];
        }

        public virtual void Load(XmlNode pXmlTerrainCustomNode)
        {

        }

        public override void Update(GameTime gameTime, Camera pActiveCamera)
        {
            Vector3 vCameraPos = pActiveCamera.Position;
            Vector3 vCameraView = pActiveCamera.View;

            // TODO swap also on X...

            if (pActiveCamera.View.Z > 0.0f)
            {
                float fFrontDistanceZ = (m_terrainPatches[0, m_nTerrainPatchCount - 1].World.M43 - pActiveCamera.Position.Z);
                if (fFrontDistanceZ < m_fHalfTerrainSizeZ)
                {
                    for (int x = 0; x < m_nTerrainPatchCount; x++)
                    {
                        Matrix mxLastWorldMatrix;
                        TerrainPatchBase swapTerrainPatch = m_terrainPatches[x, 0];

                        for (int z = 0; z < m_nTerrainPatchCount - 1; z++)
                            m_terrainPatches[x, z] = m_terrainPatches[x, z + 1];

                        mxLastWorldMatrix = m_terrainPatches[x, m_nTerrainPatchCount - 1].World;

                        m_terrainPatches[x, m_nTerrainPatchCount - 1] = swapTerrainPatch;
                        m_terrainPatches[x, m_nTerrainPatchCount - 1].World = Matrix.CreateTranslation(mxLastWorldMatrix.M41, m_fTranslateY, mxLastWorldMatrix.M43 + m_fTerrainPatchSizeZ);
                    }
                }
            }
            else
            {
                float fBackDistanceZ = Math.Abs(m_terrainPatches[0, 0].World.M43 - m_fTerrainPatchSizeZ - pActiveCamera.Position.Z);
                if (fBackDistanceZ < m_fHalfTerrainSizeZ)
                {
                    for (int x = 0; x < m_nTerrainPatchCount; x++)
                    {
                        Matrix mxLastWorldMatrix;
                        TerrainPatchBase swapTerrainPatch = m_terrainPatches[x, m_nTerrainPatchCount - 1];

                        for (int z = m_nTerrainPatchCount - 1; z > 0; z--)
                            m_terrainPatches[x, z] = m_terrainPatches[x, z - 1];

                        mxLastWorldMatrix = m_terrainPatches[x, 0].World;

                        m_terrainPatches[x, 0] = swapTerrainPatch;
                        m_terrainPatches[x, 0].World = Matrix.CreateTranslation(mxLastWorldMatrix.M41, m_fTranslateY, mxLastWorldMatrix.M43 - m_fTerrainPatchSizeZ);
                    }
                }
            }

            base.Update(gameTime, pActiveCamera);
        }

        public override void Draw(GameTime gameTime, Camera pActiveCamera)
        {

        }
    }
    #endregion

    #region HeightmapTerrain
    public class HeightmapTerrain : InfiniteTerrainBase
    {
        #region HeightmapTerrainPatch
        private class HeightmapTerrainPatch : TerrainPatchBase
        {
            private int m_nPatchSize;
            private VertexBuffer m_patchVertexBuffer;
            private int m_nPatchTriangleCount;

            public HeightmapTerrainPatch(BaseGame pGame)
                : base(pGame)
            {

            }

            private Vector3 CalculateNormal(int x, int z, int nPatchSize, float fTerrainScaleX, float fTerrainScaleY, float fTerrainScaleZ, float[,] pHeightmap)
            {
                Vector3 vNormal;

                if ((x < (nPatchSize - 1)) && (z < (nPatchSize - 1)))
                {
                    Vector3 vPivot = new Vector3(x * fTerrainScaleX, pHeightmap[x, z], z * fTerrainScaleZ);
                    Vector3 vEdge1 = new Vector3(x * fTerrainScaleX, pHeightmap[x, z + 1], (z + 1) * fTerrainScaleZ);
                    Vector3 vEdge2 = new Vector3((x + 1) * fTerrainScaleX, pHeightmap[x + 1, z], z * fTerrainScaleZ);

                    vNormal = Vector3.Cross(vEdge1, vEdge2);
                    vNormal.Normalize();
                }
                else
                {
                    vNormal.X = 0.0f;
                    vNormal.Y = 1.0f;
                    vNormal.Z = 0.0f;
                }

                return vNormal;
            }

            public void Initialize(GraphicsDevice pGraphicsDevice, float[,] pHeightmap, int nTerrainSize, int nPatchSize, int nBaseX, int nBaseZ, float fTerrainScaleX, float fTerrainScaleY, float fTerrainScaleZ, float fTextureStepX, float fTextureStepZ)
            {
                Vector3 vMin = new Vector3(0.0f, 0.0f, 0.0f);
                Vector3 vMax = new Vector3(0.0f, 0.0f, 0.0f);
                VertexPositionNormalTexture[] vTriangle = new VertexPositionNormalTexture[3];

                m_nPatchSize = nPatchSize;
                m_nPatchTriangleCount = (m_nPatchSize) * (m_nPatchSize) * 2;

                // offset to the start position into stream... 
                int nIndex = 0;
                VertexPositionNormalTexture[] patchMesh = new VertexPositionNormalTexture[m_nPatchTriangleCount * 3];
                for (int z = 0; z < m_nPatchSize; z++)
                {
                    for (int x = 0; x < m_nPatchSize; x++)
                    {
                        // first triangle of quad...
                        vTriangle[0].Position.X = x * fTerrainScaleX;
                        vTriangle[0].Position.Y = pHeightmap[x + nBaseX, z + nBaseZ];
                        vTriangle[0].Position.Z = z * fTerrainScaleZ;
                        vTriangle[0].TextureCoordinate.X = x * fTextureStepX;
                        vTriangle[0].TextureCoordinate.Y = z * fTextureStepZ;

                        vTriangle[1].Position.X = x * fTerrainScaleX;
                        vTriangle[1].Position.Y = pHeightmap[x + nBaseX, z + nBaseZ + 1];
                        vTriangle[1].Position.Z = (z + 1) * fTerrainScaleZ;
                        vTriangle[1].TextureCoordinate.X = x * fTextureStepX;
                        vTriangle[1].TextureCoordinate.Y = (z + 1) * fTextureStepZ;

                        vTriangle[2].Position.X = (x + 1) * fTerrainScaleX;
                        vTriangle[2].Position.Y = pHeightmap[x + nBaseX + 1, z + nBaseZ + 1];
                        vTriangle[2].Position.Z = (z + 1) * fTerrainScaleZ;
                        vTriangle[2].TextureCoordinate.X = (x + 1) * fTextureStepX;
                        vTriangle[2].TextureCoordinate.Y = (z + 1) * fTextureStepZ;

                        vTriangle[0].Normal = CalculateNormal(x + nBaseX, z + nBaseZ, nTerrainSize, fTerrainScaleX, fTerrainScaleY, fTerrainScaleZ, pHeightmap);
                        vTriangle[1].Normal = CalculateNormal(x + nBaseX, z + nBaseZ + 1, nTerrainSize, fTerrainScaleX, fTerrainScaleY, fTerrainScaleZ, pHeightmap);
                        vTriangle[2].Normal = CalculateNormal(x + nBaseX + 1, z + nBaseZ + 1, nTerrainSize, fTerrainScaleX, fTerrainScaleY, fTerrainScaleZ, pHeightmap);

                        patchMesh[nIndex++] = vTriangle[0];
                        patchMesh[nIndex++] = vTriangle[1];
                        patchMesh[nIndex++] = vTriangle[2];

                        // second triangle of quad...
                        vTriangle[0].Position.X = x * fTerrainScaleX;
                        vTriangle[0].Position.Y = pHeightmap[x + nBaseX, z + nBaseZ];
                        vTriangle[0].Position.Z = z * fTerrainScaleZ;
                        vTriangle[0].TextureCoordinate.X = x * fTextureStepX;
                        vTriangle[0].TextureCoordinate.Y = z * fTextureStepZ;

                        vTriangle[1].Position.X = (x + 1) * fTerrainScaleX;
                        vTriangle[1].Position.Y = pHeightmap[x + nBaseX + 1, z + nBaseZ + 1];
                        vTriangle[1].Position.Z = (z + 1) * fTerrainScaleZ;
                        vTriangle[1].TextureCoordinate.X = (x + 1) * fTextureStepX;
                        vTriangle[1].TextureCoordinate.Y = (z + 1) * fTextureStepZ;

                        vTriangle[2].Position.X = (x + 1) * fTerrainScaleX;
                        vTriangle[2].Position.Y = pHeightmap[x + nBaseX + 1, z + nBaseZ];
                        vTriangle[2].Position.Z = z * fTerrainScaleZ;
                        vTriangle[2].TextureCoordinate.X = (x + 1) * fTextureStepX;
                        vTriangle[2].TextureCoordinate.Y = z * fTextureStepZ;

                        vTriangle[0].Normal = CalculateNormal(x + nBaseX, z + nBaseZ, nTerrainSize, fTerrainScaleX, fTerrainScaleY, fTerrainScaleZ, pHeightmap);
                        vTriangle[1].Normal = CalculateNormal(x + nBaseX + 1, z + nBaseZ + 1, nTerrainSize, fTerrainScaleX, fTerrainScaleY, fTerrainScaleZ, pHeightmap);
                        vTriangle[2].Normal = CalculateNormal(x + nBaseX + 1, z + nBaseZ, nTerrainSize, fTerrainScaleX, fTerrainScaleY, fTerrainScaleZ, pHeightmap);

                        patchMesh[nIndex++] = vTriangle[0];
                        patchMesh[nIndex++] = vTriangle[1];
                        patchMesh[nIndex++] = vTriangle[2];
                    }
                }

                m_patchVertexBuffer = new VertexBuffer(pGraphicsDevice, VertexPositionNormalTexture.SizeInBytes * m_nPatchTriangleCount * 3, ResourceUsage.None, ResourceManagementMode.Automatic);
                m_patchVertexBuffer.SetData<VertexPositionNormalTexture>(patchMesh);
            }

            public override void Draw(GameTime gameTime, GraphicsDevice pGraphicsDevice, Camera pActiveCamera)
            {
                pGraphicsDevice.Vertices[0].SetSource(m_patchVertexBuffer, 0, VertexPositionNormalTexture.SizeInBytes);
                pGraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, m_nPatchTriangleCount);
            }
        }
        #endregion

        #region Private Attributes
        private Texture2D m_Texture;
        private Texture2D m_DetailTexture;
        private VertexDeclaration m_terrainVertexDecl;
        #endregion

        public HeightmapTerrain() : base()
        {

        }

        public override void Load(XmlNode pXmlTerrainCustomNode)
        {
            m_Texture = m_Game.ContentManager.Load<Texture2D>(pXmlTerrainCustomNode.SelectSingleNode("Texture").InnerText);
            m_DetailTexture = m_Game.ContentManager.Load<Texture2D>(pXmlTerrainCustomNode.SelectSingleNode("TextureDetail").InnerText);
            m_customEffect = m_Game.ContentManager.Load<Effect>(pXmlTerrainCustomNode.SelectSingleNode("Shader").InnerText);

            String terrainHeightmapFile = pXmlTerrainCustomNode.SelectSingleNode("Heightmap").InnerText;
            String[] split;
            split = pXmlTerrainCustomNode.SelectSingleNode("TextureRepeat").InnerText.Split(' ');

            float fTextureRepeatX = Convert.ToSingle(split[0]);
            float fTextureRepeatZ = Convert.ToSingle(split[1]);

            FileStream rawFile = new FileStream(terrainHeightmapFile, FileMode.Open);
            BinaryReader binReader = new BinaryReader(rawFile);

            // load terrain mesh
            float fStepX = fTextureRepeatX / m_nTerrainSize;
            float fStepZ = fTextureRepeatZ / m_nTerrainSize;

            float[,] fHeightmap = new float[m_nTerrainSize, m_nTerrainSize];
            for (int z = 0; z < m_nTerrainSize; z++)
            {
                for (int x = 0; x < m_nTerrainSize; x++)
                {
                    fHeightmap[x, z] = binReader.ReadByte() * m_fTerrainScaleY;
                }
            }

            int nPatchSize = m_nTerrainSize / m_nTerrainPatchCount;

            m_terrainVertexDecl = new VertexDeclaration(m_Game.GraphicsDevice, VertexPositionNormalTexture.VertexElements);

            for (int z = 0; z < m_nTerrainPatchCount; z++)
            {
                for (int x = 0; x < m_nTerrainPatchCount; x++)
                {
                    m_terrainPatches[x, z] = new HeightmapTerrainPatch(m_Game);
                    m_terrainPatches[x, z].World = Matrix.CreateTranslation(-(m_nTerrainPatchCount / 2 - x) * m_fTerrainPatchSizeX,
                                                                            m_fTranslateY,
                                                                            -(m_nTerrainPatchCount / 2 - z) * m_fTerrainPatchSizeZ);
                    ((HeightmapTerrainPatch)m_terrainPatches[x, z]).Initialize(m_Game.GraphicsDevice,
                                                                                 fHeightmap,
                                                                                 m_nTerrainSize,
                                                                                 nPatchSize,
                                                                                 Math.Max(x * nPatchSize - 1, 0),
                                                                                 Math.Max(z * nPatchSize - 1, 0),
                                                                                 m_fTerrainScaleX,
                                                                                 m_fTerrainScaleY,
                                                                                 m_fTerrainScaleZ,
                                                                                 fStepX,
                                                                                 fStepZ);
                }
            }
        }

        public override void Draw(GameTime gameTime, Camera pActiveCamera)
        {
            Matrix mxView = Matrix.CreateLookAt(pActiveCamera.Position, pActiveCamera.LookAt, pActiveCamera.Up);
            Matrix mxProjection = Matrix.CreatePerspectiveFieldOfView(pActiveCamera.FOV, pActiveCamera.AspectRatio, pActiveCamera.NearPlane, pActiveCamera.FarPlane);

            m_Game.GraphicsDevice.VertexDeclaration = m_terrainVertexDecl;
            m_Game.GraphicsDevice.RenderState.CullMode = CullMode.None;
            m_Game.GraphicsDevice.RenderState.AlphaBlendEnable = false;
            m_Game.GraphicsDevice.RenderState.DepthBufferEnable = true;
            m_Game.GraphicsDevice.RenderState.DepthBufferWriteEnable = true;

            m_customEffect.Parameters["View"].SetValue(mxView);
            m_customEffect.Parameters["Projection"].SetValue(mxProjection);

            m_customEffect.Parameters["TerrainTexture"].SetValue(m_Texture);
            m_customEffect.Parameters["DetailTexture"].SetValue(m_DetailTexture);
            m_customEffect.Parameters["DetailTextureCoordScale"].SetValue(16.0f);

            m_customEffect.Parameters["LightDirection"].SetValue(-m_vLightDirection);
            m_customEffect.Parameters["LightAmbient"].SetValue(m_vLightAmbient);
            m_customEffect.Parameters["LightDiffuse"].SetValue(m_vLightDiffuse);

            m_customEffect.Begin();
            foreach (EffectPass pass in m_customEffect.CurrentTechnique.Passes)
            {
                for (int z = 0; z < m_nTerrainPatchCount; z++)
                {
                    for (int x = 0; x < m_nTerrainPatchCount; x++)
                    {
                        if (m_terrainPatches[x, z] != null)
                        {
                            m_customEffect.Parameters["World"].SetValue(m_terrainPatches[x, z].World);
                            m_customEffect.Parameters["InvWorld"].SetValue(Matrix.Invert(m_terrainPatches[x, z].World));

                            pass.Begin();
                            m_terrainPatches[x, z].Draw(gameTime, m_Game.GraphicsDevice, pActiveCamera);
                            pass.End();
                        }
                    }
                }
            }
            m_customEffect.End();
        }

    }
    #endregion

    #region ModelsTerrain
    public class ModelsTerrain : InfiniteTerrainBase
    {
        #region ModelTerrainPatch
        private class ModelTerrainPatch : TerrainPatchBase
        {
            private MOODModel m_patchModel;

            public ModelTerrainPatch(BaseGame pGame, String pPatchModelFile)
                : base(pGame)
            {
                m_patchModel = new MOODModel(pGame, pPatchModelFile);
            }

            public override void Draw(GameTime gameTime, GraphicsDevice pGraphicsDevice, Camera pActiveCamera)
            {
                if (m_patchModel == null)
                    return;

                // TODO optimize this...
                m_patchModel.WorldMatrix = m_mxWorld;
                m_patchModel.ViewMatrix = Matrix.CreateLookAt(pActiveCamera.Position, pActiveCamera.LookAt, pActiveCamera.Up);
                m_patchModel.ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(pActiveCamera.FOV, pActiveCamera.AspectRatio, pActiveCamera.NearPlane, pActiveCamera.FarPlane);

                m_patchModel.Draw(gameTime, pActiveCamera);
            }
        }
        #endregion

        #region Private Attributes...

        #endregion

        public ModelsTerrain()
        {

        }

        public override void Load(XmlNode pXmlTerrainCustomNode)
        {
            // fillup terrain with base patches...
            for (int z = 0; z < m_nTerrainPatchCount; z++)
            {
                for (int x = 0; x < m_nTerrainPatchCount; x++)
                {
                    m_terrainPatches[x, z] = new TerrainPatchBase(m_Game);
                    m_terrainPatches[x, z].World = Matrix.CreateTranslation(-(m_nTerrainPatchCount / 2 - x) * m_fTerrainPatchSizeX,
                                                                            m_fTranslateY,
                                                                            -(m_nTerrainPatchCount / 2 - z) * m_fTerrainPatchSizeZ);

                }
            }

            // add custom model patch as described in xml file...
            String graphicsPath = pXmlTerrainCustomNode.SelectSingleNode("TexturePath").InnerText;
            XmlNode xmlModelPatches = pXmlTerrainCustomNode.SelectSingleNode("ModelPatches");
            foreach (XmlNode xmlModelNode in xmlModelPatches.ChildNodes)
            {
                int posX = Convert.ToInt32(xmlModelNode.Attributes["PosX"].Value);
                int posZ = Convert.ToInt32(xmlModelNode.Attributes["PosZ"].Value);
                String patchModelFile = xmlModelNode.InnerText;

                m_terrainPatches[posX, posZ] = new ModelTerrainPatch(m_Game, patchModelFile);
                m_terrainPatches[posX, posZ].World = Matrix.CreateTranslation(-(m_nTerrainPatchCount / 2 - posX) * m_fTerrainPatchSizeX,
                                                                              m_fTranslateY,
                                                                              -(m_nTerrainPatchCount / 2 - posZ) * m_fTerrainPatchSizeZ);
            }
        }

        public override void Draw(GameTime gameTime, Camera pActiveCamera)
        {
            for (int z = 0; z < m_nTerrainPatchCount; z++)
            {
                for (int x = 0; x < m_nTerrainPatchCount; x++)
                    m_terrainPatches[x, z].Draw(gameTime, m_Game.GraphicsDevice, pActiveCamera);
            }
        }
    }

    #endregion

    #region MOODTerrain
    public class MOODTerrain : InfiniteTerrainBase
    {
        #region MOODTerrainPatch
        private class MOODTerrainPatch : TerrainPatchBase
        {
            private int m_nPatchTriangleCount;
            private VertexBuffer m_patchVertexBuffer;
            private BoundingBoxEx m_patchBoundingBox;
            private Collection<MOODTriangle> m_triangleList = new Collection<MOODTriangle>();
            private float m_fPatchWidth;
            private float m_fPatchHeight;

            public MOODTerrainPatch(BaseGame pGame, float fMinX, float fMinY, float fMinZ, float fPatchWidth, float fPatchHeight)
                : base(pGame)
            {
                m_nPatchTriangleCount = 0;
                m_fPatchWidth = fPatchWidth;
                m_fPatchHeight = fPatchHeight;
                m_patchBoundingBox = new BoundingBoxEx(new Vector3(fMinX, fMinY, fMinZ), new Vector3(fMinX + fPatchWidth, fMinY, fMinZ + fPatchHeight));
            }

            public bool AddTriangle(MOODTriangle pMOODTriangle)
            {
                // check if the triangle is inside the patch bounding box...
                bool isInside = (
                                 ((pMOODTriangle.V0.X >= m_patchBoundingBox.Min.X) && (pMOODTriangle.V0.X <= m_patchBoundingBox.Max.X) && (pMOODTriangle.V0.Z >= m_patchBoundingBox.Min.Z) && (pMOODTriangle.V0.Z <= m_patchBoundingBox.Max.Z)) ||
                                 ((pMOODTriangle.V1.X >= m_patchBoundingBox.Min.X) && (pMOODTriangle.V1.X <= m_patchBoundingBox.Max.X) && (pMOODTriangle.V1.Z >= m_patchBoundingBox.Min.Z) && (pMOODTriangle.V1.Z <= m_patchBoundingBox.Max.Z)) ||
                                 ((pMOODTriangle.V2.X >= m_patchBoundingBox.Min.X) && (pMOODTriangle.V2.X <= m_patchBoundingBox.Max.X) && (pMOODTriangle.V2.Z >= m_patchBoundingBox.Min.Z) && (pMOODTriangle.V2.Z <= m_patchBoundingBox.Max.Z))
                                );

                if (isInside)
                    m_triangleList.Add(pMOODTriangle);

                return isInside;
            }

            private Vector3 CalculateNormal(VertexPositionNormalTexture pV0, VertexPositionNormalTexture pV1, VertexPositionNormalTexture pV2)
            {
                Vector3 vNormal;

                Vector3 vPivot = pV0.Position;
                Vector3 vEdge1 = pV1.Position;
                Vector3 vEdge2 = pV2.Position;

                vNormal = Vector3.Cross(vEdge1, vEdge2);
                vNormal.Normalize();

                return vNormal;
            }

            public void CreateMesh()
            {
                m_nPatchTriangleCount = m_triangleList.Count;

                int nIndex = 0;
                VertexPositionNormalTexture[] vTriangle = new VertexPositionNormalTexture[3];
                VertexPositionNormalTexture[] patchMesh = new VertexPositionNormalTexture[m_nPatchTriangleCount * 3];                
                foreach(MOODTriangle moodTriangle in m_triangleList)
                {
                    vTriangle[0].Position.X = moodTriangle.V0.X - m_patchBoundingBox.Min.X;
                    vTriangle[0].Position.Y = moodTriangle.V0.Y;
                    vTriangle[0].Position.Z = moodTriangle.V0.Z - m_patchBoundingBox.Min.Z;
                    vTriangle[0].Normal = moodTriangle.Normal0;
                    vTriangle[0].TextureCoordinate.X = vTriangle[0].Position.X / m_fPatchWidth;
                    vTriangle[0].TextureCoordinate.Y = vTriangle[0].Position.Z / m_fPatchHeight;

                    vTriangle[1].Position.X = moodTriangle.V1.X - m_patchBoundingBox.Min.X;
                    vTriangle[1].Position.Y = moodTriangle.V1.Y;
                    vTriangle[1].Position.Z = moodTriangle.V1.Z - m_patchBoundingBox.Min.Z;
                    vTriangle[1].Normal = moodTriangle.Normal1;
                    vTriangle[1].TextureCoordinate.X = vTriangle[1].Position.X / m_fPatchWidth;
                    vTriangle[1].TextureCoordinate.Y = vTriangle[1].Position.Z / m_fPatchHeight;

                    vTriangle[2].Position.X = moodTriangle.V2.X - m_patchBoundingBox.Min.X;
                    vTriangle[2].Position.Y = moodTriangle.V2.Y;
                    vTriangle[2].Position.Z = moodTriangle.V2.Z - m_patchBoundingBox.Min.Z;
                    vTriangle[2].Normal = moodTriangle.Normal2;
                    vTriangle[2].TextureCoordinate.X = vTriangle[2].Position.X / m_fPatchWidth;
                    vTriangle[2].TextureCoordinate.Y = vTriangle[2].Position.Z / m_fPatchHeight;

                    patchMesh[nIndex++] = vTriangle[0];
                    patchMesh[nIndex++] = vTriangle[1];
                    patchMesh[nIndex++] = vTriangle[2];
                }

                m_patchVertexBuffer = new VertexBuffer(m_Game.GraphicsDevice, VertexPositionNormalTexture.SizeInBytes * m_nPatchTriangleCount * 3, ResourceUsage.None, ResourceManagementMode.Automatic);
                m_patchVertexBuffer.SetData<VertexPositionNormalTexture>(patchMesh);
            }

            public override void Draw(GameTime gameTime, GraphicsDevice pGraphicsDevice, Camera pActiveCamera)
            {
                pGraphicsDevice.Vertices[0].SetSource(m_patchVertexBuffer, 0, VertexPositionNormalTexture.SizeInBytes);
                pGraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, m_nPatchTriangleCount);
            }
        }
        #endregion

        #region Private Attributes...
        private VertexDeclaration m_terrainVertexDecl;
        private Texture2D m_Texture;
        private Texture2D m_DetailTexture;
        private float m_fTerrainWidth;
        private float m_fTerrainHeight;
        private float m_fTerrainPatchWidth;
        private float m_fTerrainPatchHeight;
        private Matrix m_mxScale;
        #endregion

        public MOODTerrain()
        {

        }

        public override void Load(XmlNode pXmlTerrainCustomNode)
        {
            String moodTerrainModelFile = pXmlTerrainCustomNode.SelectSingleNode("Model").InnerText;
            MOODModel moodTerrainModel = new MOODModel(m_Game, moodTerrainModelFile);
            
            m_customEffect = m_Game.ContentManager.Load<Effect>(pXmlTerrainCustomNode.SelectSingleNode("Shader").InnerText);
            m_terrainVertexDecl = new VertexDeclaration(m_Game.GraphicsDevice, VertexPositionNormalTexture.VertexElements);

            m_Texture = m_Game.ContentManager.Load<Texture2D>(pXmlTerrainCustomNode.SelectSingleNode("Texture").InnerText);
            m_DetailTexture = m_Game.ContentManager.Load<Texture2D>(pXmlTerrainCustomNode.SelectSingleNode("TextureDetail").InnerText);

            m_fTerrainWidth = (moodTerrainModel.BoundingBox.Max.X - moodTerrainModel.BoundingBox.Min.X);
            m_fTerrainHeight = (moodTerrainModel.BoundingBox.Max.Z - moodTerrainModel.BoundingBox.Min.Z);

            m_fTerrainPatchWidth = m_fTerrainWidth / m_nTerrainPatchCount;
            m_fTerrainPatchHeight = m_fTerrainHeight / m_nTerrainPatchCount;

            // create terrain patches...
            for (int z = 0; z < m_nTerrainPatchCount; z++)
            {
                for (int x = 0; x < m_nTerrainPatchCount; x++)
                {
                    m_terrainPatches[x, z] = new MOODTerrainPatch(m_Game,
                                                                  moodTerrainModel.BoundingBox.Min.X + (m_fTerrainPatchWidth * x),
                                                                  0.0f,
                                                                  moodTerrainModel.BoundingBox.Min.Z + (m_fTerrainPatchHeight * z),
                                                                  m_fTerrainPatchWidth,
                                                                  m_fTerrainPatchHeight);
                }
            }

            // fill triangles in patches...
            foreach (MOODTriangle moodTriangle in moodTerrainModel.Triangles)
            {
                for (int z = 0; z < m_nTerrainPatchCount; z++)
                {
                    for (int x = 0; x < m_nTerrainPatchCount; x++)
                    {
                        ((MOODTerrainPatch)m_terrainPatches[x, z]).AddTriangle(moodTriangle);
                    }
                }
            }

            // create the mesh inside the patches...
            for (int z = 0; z < m_nTerrainPatchCount; z++)
            {
                for (int x = 0; x < m_nTerrainPatchCount; x++)
                {
                    ((MOODTerrainPatch)m_terrainPatches[x, z]).CreateMesh();
                    ((MOODTerrainPatch)m_terrainPatches[x, z]).World = Matrix.CreateTranslation(moodTerrainModel.BoundingBox.Min.X + (m_fTerrainPatchWidth * x),
                                                                                                m_fTranslateY,
                                                                                                moodTerrainModel.BoundingBox.Min.Z + (m_fTerrainPatchHeight * z));
                }
            }

            m_mxScale = Matrix.CreateScale(m_fTerrainScaleX, m_fTerrainScaleY, m_fTerrainScaleZ);

            m_fHalfTerrainSizeX = m_fTerrainWidth * 0.5f;
            m_fHalfTerrainSizeZ = m_fTerrainHeight * 0.5f;

            m_fTerrainPatchSizeX = m_fTerrainPatchWidth;
            m_fTerrainPatchSizeZ = m_fTerrainPatchHeight;
        }

        public override void Draw(GameTime gameTime, Camera pActiveCamera)
        {
            Matrix mxView = Matrix.CreateLookAt(pActiveCamera.Position, pActiveCamera.LookAt, pActiveCamera.Up);
            Matrix mxProjection = Matrix.CreatePerspectiveFieldOfView(pActiveCamera.FOV, pActiveCamera.AspectRatio, pActiveCamera.NearPlane, pActiveCamera.FarPlane);

            m_Game.GraphicsDevice.VertexDeclaration = m_terrainVertexDecl;
            m_Game.GraphicsDevice.RenderState.CullMode = CullMode.CullClockwiseFace;
            m_Game.GraphicsDevice.RenderState.AlphaBlendEnable = false;
            m_Game.GraphicsDevice.RenderState.DepthBufferEnable = true;
            m_Game.GraphicsDevice.RenderState.DepthBufferWriteEnable = true;

            m_customEffect.Parameters["View"].SetValue(mxView);
            m_customEffect.Parameters["Projection"].SetValue(mxProjection);

            m_customEffect.Parameters["TerrainTexture"].SetValue(m_Texture);
            m_customEffect.Parameters["DetailTexture"].SetValue(m_DetailTexture);
            m_customEffect.Parameters["DetailTextureCoordScale"].SetValue(16.0f);

            m_customEffect.Parameters["LightDirection"].SetValue(-m_vLightDirection);
            m_customEffect.Parameters["LightAmbient"].SetValue(m_vLightAmbient);
            m_customEffect.Parameters["LightDiffuse"].SetValue(m_vLightDiffuse);

            m_customEffect.Begin();
            foreach (EffectPass pass in m_customEffect.CurrentTechnique.Passes)
            {
                for (int z = 0; z < m_nTerrainPatchCount; z++)
                {
                    for (int x = 0; x < m_nTerrainPatchCount; x++)
                    {
                        if (m_terrainPatches[x, z] != null)
                        {
                            m_customEffect.Parameters["World"].SetValue(m_mxScale * m_terrainPatches[x, z].World);
                            m_customEffect.Parameters["InvWorld"].SetValue(Matrix.Invert(m_terrainPatches[x, z].World));

                            pass.Begin();
                            m_terrainPatches[x, z].Draw(gameTime, m_Game.GraphicsDevice, pActiveCamera);
                            pass.End();
                        }
                    }
                }
            }
            m_customEffect.End();
        }
    }

    #endregion

}
